#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# This program is free software; you can redistribute it and/or modify  
# it under the terms of the GNU General Public License as published by  
# the Free Software Foundation; either version 2 of the License, or     
# (at your option) any later version.                                   
#                                                                         
# A copy of the license can be found in the license.txt file supplied   
# with this software or at: http://www.gnu.org/copyleft/gpl.html       
#

try :
    from hashlib import md5
except :
    from md5 import md5


from id import ID
from url import URL
from node import Node
from tcpendpoint import TcpEndPoint
from udpendpoint import UdpEndPoint
from tcpproxy import TcpProxy
from routine import Routine
from stablizer import Stablizer

from help import logging

class   Chord(object) :

    class   MD5Hash(object) :
        def __init__(self) :
            self.bit_count = ID.LENGTH
            cut_size,rshift_size = self.__getCutAndShiftSize( self.bit_count )
            self.cut_size = cut_size
            self.rshift_size = rshift_size
            ## __init__()

        def __getCutAndShiftSize(self, bit_count) :
            r = bit_count % 4 
            cut_size = bit_count / 4 
            if r : cut_size += 1
            rshift_size = cut_size * 4 - bit_count
            return cut_size, rshift_size
            ## __getCutAndShiftSize()

        def __call__(self, str) :
            '''calculate hash using md5 algorithm

            @param str key to be calculated (string)

            @return x hash value (int)
            '''
            m = md5(str).hexdigest()
            x = int(m[ :self.cut_size ], 16) >> self.rshift_size
            return x
            ## __call__()

        ## class MD5Hash

    def __init__(self) :
        self.local_url = None
        self.local_id = 0
        self.tcp_endpoint = None
        self.udp_endpoint = None
        self.node = None
        self.routine = None
        self.stablizer = None

        self.hash = self.__class__.MD5Hash()

        self.logger = logging.getLogger('OpenChord')
        self.logger.debug('Chord.__init__()')
        ## __init__()

    def __del__(self) :
        if self.udp_endpoint :
            self.udp_endpoint.close()
        if self.tcp_endpoint :
            self.tcp_endpoint.close()
        if self.routine :
            self.routine.stop()
        if self.stablizer :
            self.stablizer.stop()

        self.local_url = None
        self.local_id = 0
        self.tcp_endpoint = None
        self.udp_endpoint = None
        self.routine = None
        self.node = None
        self.logger.debug('Chord.__del__()')
        ## __del__()

    def join(self, bootstrap_urls, local_url, local_id=None) :
        '''join chord ring
        '''
        if self.tcp_endpoint :
            raise RuntimeError,'Already join chord as node "%s"' % self.local_url.getURL()

        self.local_url = URL(local_url)
        if not (isinstance(local_id, int) and local_id >= ID.MINIMUM and local_id < ID.MAXIMUM) :
            raise ValueError, 'Invalid local ID'
        self.local_id = ID(local_id)

        # create node
        self.node = Node(self.local_url, self.local_id)

        # create endpoint
        try :
            self.tcp_endpoint = TcpEndPoint(self.local_url, self.node)
            self.tcp_endpoint.open()
        except :
            self.tcp_endpoint = None
            self.node = None
            raise

        try :
            self.udp_endpoint = UdpEndPoint(self.local_url, self.node)
            self.udp_endpoint.open()
        except :
            self.udp_endpoint = None
            self.node = None
            raise

        # create routine
        try :
            self.routine = Routine(self.node)
            self.routine.setDaemon( True )
            self.routine.start()
        except :
            self.routine = None
            self.node = None
            raise

        # create stablizer
        try :
            self.stablizer = Stablizer(self.node)
            self.stablizer.setDaemon( True )
            self.stablizer.start()
        except :
            self.stablizer = None
            self.node = None
            raise

        joined = False
        if bootstrap_urls :
            # some bootstrap_urls is provided
            _bootstrap_urls = []
            if isinstance(bootstrap_urls, str) :
                _bootstrap_urls.append(URL(bootstrap_urls))
            elif isinstance(bootstrap_urls, list) or isinstance(bootstrap_urls, tuple) :
                for bootstrap_url in bootstrap_urls :
                    _bootstrap_urls.append(URL(bootstrap_url))
            else :
                raise TypeError,'bootstrap_urls must be list or tuple type.'

            for bootstrap_url in _bootstrap_urls :
                if self.helpJoin( bootstrap_url ) :
                    joined = True
                    break
        else :
            # not bootstrap_urls, I am the first node in chord ring
            self.helpCreate()
            joined = True

        return joined
        ## join()

    def helpCreate(self) :
        '''help create chord node
        '''
        return self.node.joinSelf()
        ## helpCreate()


    def helpJoin(self, bootstrap_url) :
        '''help join chord ring
        '''
        proxy = TcpProxy(bootstrap_url)
        if not proxy.ping() :
            return False

        return self.node.join(proxy)
        ## helpJoin()

    def leave(self) :
        '''leave chord ring
        '''
        if self.routine :
            self.routine.stop()
            self.routine = None

        if self.stablizer :
            self.stablizer.stop()
            self.stablizer = None

        if self.tcp_endpoint :
            if self.logger.isDebugEnabled() : self.logger.debug('chord #%d is leaving Chord Ring/TCP.', self.local_id.getID())
            self.tcp_endpoint.close()
            self.logger.info('chord #%d leaves Chord Ring/TCP.', self.local_id.getID())
            self.tcp_endpoint = None

        if self.udp_endpoint :
            if self.logger.isDebugEnabled() : self.logger.debug('chord #%d is leaving Chord Ring/UDP.', self.local_id.getID())
            self.udp_endpoint.close()
            self.logger.info('chord #%d leaves Chord Ring/UDP.', self.local_id.getID())
            self.udp_endpoint = None

        self.local_url = None
        self.local_id = 0

        ## leave()

    #### --------------------------------------------------------- ####

    def setHash(self, hash) :
        if not callable(hash) :
            raise TypeError, 'Hash must be callable.'

        self.hash = hash
        ## setHash()

    #### --------------------------------------------------------- ####

    def toPrettyString(self) :
        if not self.node :
            return 'Chord Node : None'
        return self.node.toPrettyString()
        ## toPrettyString()

    ## class Chord

